package com.study.cacheManager;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import com.study.constant.CacheConstant;
import com.study.convention.errorCode.BaseErrorCode;
import com.study.convention.exception.ServiceException;
import com.study.dao.entity.TLinkAccessStats;
import com.study.dao.entity.TLinkDeviceStats;
import com.study.dao.entity.TLinkLocaleStats;
import com.study.dao.entity.TLinkNetworkStats;
import com.study.dao.mapper.*;
import com.study.dto.req.ShortLinkGroupStatsReqDTO;
import com.study.dto.req.ShortLinkStatsReqDTO;
import com.study.dto.resp.*;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author canace
 * @version 1.0
 * @description 短链接统计信息缓存管理
 * @date 2024/2/14 18:47
 */

@Component
@RequiredArgsConstructor
public class LinkStatsCacheManager {


    private final TLinkAccessStatsMapper linkAccessStatsMapper;
    private final TLinkLocaleStatsMapper linkLocaleStatsMapper;
    private final TLinkAccessLogsMapper linkAccessLogsMapper;
    private final TLinkBrowserStatsMapper linkBrowserStatsMapper;
    private final TLinkOsStatsMapper linkOsStatsMapper;
    private final TLinkDeviceStatsMapper linkDeviceStatsMapper;
    private final TLinkNetworkStatsMapper linkNetworkStatsMapper;
    private final TLinkStatsTodayMapper linkStatsTodayMapper;

    @Cacheable(cacheManager = CacheConstant.REDIS_CACHE_MANAGER, cacheNames = CacheConstant.LINK_STATS_BY_SHORT_LINK_NAME)
    public ShortLinkStatsRespDTO shortLinkStats(ShortLinkStatsReqDTO reqDTO){
        // 查询短链接访问统计
        List<TLinkAccessStats> tLinkAccessStats = linkAccessStatsMapper.listStatsByShortLink(reqDTO);
        if (CollUtil.isEmpty(tLinkAccessStats)) {
            throw new ServiceException(BaseErrorCode.STATS_QUERY_ERROR);
        }
        // 根据日期访问指定时间的pv,uv, uip
        TLinkAccessStats pvUvUidStatsByShortLink = linkAccessLogsMapper.findPvUvUidStatsByShortLink(reqDTO);
        List<ShortLinkStatsAccessDailyRespDTO> daily = new ArrayList<>();
        // 日期范围生成器（按日划分）
        List<String> rangeDates = DateUtil.rangeToList(DateUtil.parse(reqDTO.getStartDate()), DateUtil.parse(reqDTO.getEndDate()), DateField.DAY_OF_MONTH).stream().map(DateUtil::formatDate).toList();
        // 基础访问详情
        rangeDates.forEach(each -> tLinkAccessStats.stream()
                .filter(item -> Objects.equals(each, DateUtil.formatDate(item.getDate())))
                .findFirst()
                .ifPresentOrElse(item ->
                                daily.add(ShortLinkStatsAccessDailyRespDTO.builder().date(each).pv(item.getPv()).uv(item.getUv()).uip(item.getUip()).build()),
                        () -> daily.add(ShortLinkStatsAccessDailyRespDTO.builder().date(each).pv(0).uv(0).uip(0).build())));

        // 地区访问详情
        List<TLinkLocaleStats> tLinkLocaleStats = linkLocaleStatsMapper.listLocaleByShortLink(reqDTO);
        // 地区访问总数
        List<ShortLinkStatsLocaleCNRespDTO> localeCntStats = new ArrayList<>();
        int localeCntSum = tLinkLocaleStats.stream().mapToInt(TLinkLocaleStats::getCnt).sum();
        tLinkLocaleStats.forEach(item -> {
            double ratio = (double) item.getCnt() / localeCntSum;
            // 如果是整数，则返回整数，只有本身是小数的情况下，才能返回小数
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            localeCntStats.add(ShortLinkStatsLocaleCNRespDTO.builder().cnt(item.getCnt()).locale(item.getProvince()).ratio(actualRatio).build());
        });

        // 浏览器访问详情
        List<ShortLinkStatsBrowserRespDTO> browserStats = new ArrayList<>();
        List<HashMap<String, Object>> listBrowserStatsByShortLink = linkBrowserStatsMapper.listBrowserStatsByShortLink(reqDTO);
        // 计算总数
        int browserSum = listBrowserStatsByShortLink.stream().mapToInt(item -> Integer.parseInt(item.get("count").toString())).sum();
        listBrowserStatsByShortLink.forEach(item -> {
            double ratio = (double) Integer.parseInt(item.get("count").toString()) / browserSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            browserStats.add(ShortLinkStatsBrowserRespDTO.builder().browser(item.get("browser").toString()).cnt(Integer.parseInt(item.get("count").toString())).ratio(actualRatio).build());
        });

        // 系统访问详情
        List<ShortLinkStatsOsRespDTO> osStats = new ArrayList<>();
        List<HashMap<String, Object>> listOsStatsByShortLink = linkOsStatsMapper.listOsStatsByShortLink(reqDTO);
        // 计算总数
        int osSum = listOsStatsByShortLink.stream().mapToInt(item -> Integer.parseInt(item.get("count").toString())).sum();
        listOsStatsByShortLink.forEach(item -> {
            double ratio = (double) Integer.parseInt(item.get("count").toString()) / osSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            osStats.add(ShortLinkStatsOsRespDTO.builder().os(item.get("os").toString()).cnt(Integer.parseInt(item.get("count").toString())).ratio(actualRatio).build());
        });

        // 设备访问详情
        List<ShortLinkStatsDeviceRespDTO> deviceStats = new ArrayList<>();
        List<TLinkDeviceStats> listDeviceStatsByShortLink = linkDeviceStatsMapper.listDeviceStatsByShortLink(reqDTO);
        // 计算总数
        int deviceSum = listDeviceStatsByShortLink.stream().mapToInt(TLinkDeviceStats::getCnt).sum();
        listDeviceStatsByShortLink.forEach(item -> {
            double ratio = (double) item.getCnt() / deviceSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            deviceStats.add(ShortLinkStatsDeviceRespDTO.builder().device(item.getDevice()).cnt(item.getCnt()).ratio(actualRatio).build());
        });

        // 网络访问详情
        List<ShortLinkStatsNetworkRespDTO> networkStats = new ArrayList<>();
        List<TLinkNetworkStats> listNetworkStatsByShortLink = linkNetworkStatsMapper.listNetworkStatsByShortLink(reqDTO);
        // 计算总数
        int networkSum = listNetworkStatsByShortLink.stream().mapToInt(TLinkNetworkStats::getCnt).sum();
        listNetworkStatsByShortLink.forEach(item -> {
            double ratio = (double) item.getCnt() / networkSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            networkStats.add(ShortLinkStatsNetworkRespDTO.builder().network(item.getNetwork()).cnt(item.getCnt()).ratio(actualRatio).build());
        });

        // 高频ip访问详情
        List<ShortLinkStatsTopIpRespDTO> topIpStats = new ArrayList<>();
        List<HashMap<String, Object>> listTopIpByShortLink = linkAccessLogsMapper.listTopIpByShortLink(reqDTO);
        listTopIpByShortLink.forEach(item -> {
            topIpStats.add(ShortLinkStatsTopIpRespDTO.builder().ip(item.get("ip").toString()).cnt(Integer.parseInt(item.get("count").toString())).build());
        });

        // 小时访问详情
        List<Integer> hourStats = new ArrayList<>();
        List<TLinkAccessStats> listedHourStatsByShortLink = linkAccessStatsMapper.listHourStatsByShortLink(reqDTO);
        for (int i = 0; i < 24; i++) {
            AtomicInteger hour = new AtomicInteger(i);
            int hourCnt = listedHourStatsByShortLink.stream()
                    .filter(each -> Objects.equals(each.getHour(), hour.get()))
                    .findFirst()
                    .map(TLinkAccessStats::getPv)
                    .orElse(0);
            hourStats.add(hourCnt);
        }

        // 一周访问详情
        List<Integer> weekdayStats = new ArrayList<>();
        List<TLinkAccessStats> listWeekdayStatsByShortLink = linkAccessStatsMapper.listWeekdayStatsByShortLink(reqDTO);
        for (int i = 1; i < 8; i++) {
            AtomicInteger weekday = new AtomicInteger(i);
            int weekdayCnt = listWeekdayStatsByShortLink.stream()
                    .filter(each -> Objects.equals(each.getWeekday(), weekday.get()))
                    .findFirst()
                    .map(TLinkAccessStats::getPv)
                    .orElse(0);
            weekdayStats.add(weekdayCnt);
        }

        // 访客访问类型详情
        List<ShortLinkStatsUvRespDTO> uvTypeStats = new ArrayList<>();
        HashMap<String, Object> findUvTypeCntByShortLink = linkAccessLogsMapper.findUvTypeCntByShortLink(reqDTO);
        // 获取老访客数量
        int oldUserCnt = Integer.parseInt(
                Optional.ofNullable(findUvTypeCntByShortLink)
                        .map(each -> each.get("oldUserCnt"))
                        .map(Object::toString)
                        .orElse("0")
        );
        // 获取新访客数量
        int newUserCnt = Integer.parseInt(
                Optional.ofNullable(findUvTypeCntByShortLink)
                        .map(each -> each.get("newUserCnt"))
                        .map(Object::toString)
                        .orElse("0")
        );
        int uvSum = oldUserCnt + newUserCnt;
        double oldRatio = (double) oldUserCnt / uvSum;
        double actualOldRatio = Math.round(oldRatio * 100.0) / 100.0;
        double newRatio = (double) newUserCnt / uvSum;
        double actualNewRatio = Math.round(newRatio * 100.0) / 100.0;
        ShortLinkStatsUvRespDTO newUvRespDTO = ShortLinkStatsUvRespDTO.builder()
                .uvType("newUser")
                .cnt(newUserCnt)
                .ratio(actualNewRatio)
                .build();
        uvTypeStats.add(newUvRespDTO);
        ShortLinkStatsUvRespDTO oldUvRespDTO = ShortLinkStatsUvRespDTO.builder()
                .uvType("oldUser")
                .cnt(oldUserCnt)
                .ratio(actualOldRatio)
                .build();
        uvTypeStats.add(oldUvRespDTO);

        // 封装返回结果
        return ShortLinkStatsRespDTO.builder()
                .hourStats(hourStats)
                .browserStats(browserStats)
                .osStats(osStats)
                .localeCnStats(localeCntStats)
                .deviceStats(deviceStats)
                .networkStats(networkStats)
                .pv(pvUvUidStatsByShortLink.getPv())
                .uv(pvUvUidStatsByShortLink.getUv())
                .uip(pvUvUidStatsByShortLink.getUip())
                .daily(daily)
                .topIpStats(topIpStats)
                .weekdayStats(weekdayStats)
                .uvTypeStats(uvTypeStats)
                .build();
    }

    @Cacheable(cacheManager = CacheConstant.REDIS_CACHE_MANAGER, cacheNames = CacheConstant.LINK_STATS_BY_GROUP_NAME)
    public ShortLinkStatsRespDTO groupShortLinkStats(ShortLinkGroupStatsReqDTO reqDTO){
        // 查询短链接访问统计
        List<TLinkAccessStats> tLinkAccessStats = linkAccessStatsMapper.listStatsByGroup(reqDTO);
        if (CollUtil.isEmpty(tLinkAccessStats)) {
            throw new ServiceException(BaseErrorCode.STATS_QUERY_ERROR);
        }
        // 根据日期访问指定时间的pv,uv, uip
        TLinkAccessStats pvUvUidStatsByShortLink = linkAccessLogsMapper.findPvUvUidStatsByGroup(reqDTO);
        List<ShortLinkStatsAccessDailyRespDTO> daily = new ArrayList<>();
        // 日期范围生成器（按日划分）
        List<String> rangeDates = DateUtil.rangeToList(DateUtil.parse(reqDTO.getStartDate()), DateUtil.parse(reqDTO.getEndDate()), DateField.DAY_OF_MONTH).stream().map(DateUtil::formatDate).toList();
        // 基础访问详情
        rangeDates.forEach(each -> tLinkAccessStats.stream()
                .filter(item -> Objects.equals(each, DateUtil.formatDate(item.getDate())))
                .findFirst()
                .ifPresentOrElse(item ->
                                daily.add(ShortLinkStatsAccessDailyRespDTO.builder().date(each).pv(item.getPv()).uv(item.getUv()).uip(item.getUip()).build()),
                        () -> daily.add(ShortLinkStatsAccessDailyRespDTO.builder().date(each).pv(0).uv(0).uip(0).build())));

        // 地区访问详情
        List<TLinkLocaleStats> tLinkLocaleStats = linkLocaleStatsMapper.listLocaleByGroup(reqDTO);
        // 地区访问总数
        List<ShortLinkStatsLocaleCNRespDTO> localeCntStats = new ArrayList<>();
        int localeCntSum = tLinkLocaleStats.stream().mapToInt(TLinkLocaleStats::getCnt).sum();
        tLinkLocaleStats.forEach(item -> {
            double ratio = (double) item.getCnt() / localeCntSum;
            // 如果是整数，则返回整数，只有本身是小数的情况下，才能返回小数
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            localeCntStats.add(ShortLinkStatsLocaleCNRespDTO.builder().cnt(item.getCnt()).locale(item.getProvince()).ratio(actualRatio).build());
        });

        // 浏览器访问详情
        List<ShortLinkStatsBrowserRespDTO> browserStats = new ArrayList<>();
        List<HashMap<String, Object>> listBrowserStatsByShortLink = linkBrowserStatsMapper.listBrowserStatsByGroup(reqDTO);
        // 计算总数
        int browserSum = listBrowserStatsByShortLink.stream().mapToInt(item -> Integer.parseInt(item.get("count").toString())).sum();
        listBrowserStatsByShortLink.forEach(item -> {
            double ratio = (double) Integer.parseInt(item.get("count").toString()) / browserSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            browserStats.add(ShortLinkStatsBrowserRespDTO.builder().browser(item.get("browser").toString()).cnt(Integer.parseInt(item.get("count").toString())).ratio(actualRatio).build());
        });

        // 系统访问详情
        List<ShortLinkStatsOsRespDTO> osStats = new ArrayList<>();
        List<HashMap<String, Object>> listOsStatsByShortLink = linkOsStatsMapper.listOsStatsByGroup(reqDTO);
        // 计算总数
        int osSum = listOsStatsByShortLink.stream().mapToInt(item -> Integer.parseInt(item.get("count").toString())).sum();
        listOsStatsByShortLink.forEach(item -> {
            double ratio = (double) Integer.parseInt(item.get("count").toString()) / osSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            osStats.add(ShortLinkStatsOsRespDTO.builder().os(item.get("os").toString()).cnt(Integer.parseInt(item.get("count").toString())).ratio(actualRatio).build());
        });

        // 设备访问详情
        List<ShortLinkStatsDeviceRespDTO> deviceStats = new ArrayList<>();
        List<TLinkDeviceStats> listDeviceStatsByShortLink = linkDeviceStatsMapper.listDeviceStatsByGroup(reqDTO);
        // 计算总数
        int deviceSum = listDeviceStatsByShortLink.stream().mapToInt(TLinkDeviceStats::getCnt).sum();
        listDeviceStatsByShortLink.forEach(item -> {
            double ratio = (double) item.getCnt() / deviceSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            deviceStats.add(ShortLinkStatsDeviceRespDTO.builder().device(item.getDevice()).cnt(item.getCnt()).ratio(actualRatio).build());
        });

        // 网络访问详情
        List<ShortLinkStatsNetworkRespDTO> networkStats = new ArrayList<>();
        List<TLinkNetworkStats> listNetworkStatsByShortLink = linkNetworkStatsMapper.listNetworkStatsByGroup(reqDTO);
        // 计算总数
        int networkSum = listNetworkStatsByShortLink.stream().mapToInt(TLinkNetworkStats::getCnt).sum();
        listNetworkStatsByShortLink.forEach(item -> {
            double ratio = (double) item.getCnt() / networkSum;
            double actualRatio = Math.round(ratio * 100.0) / 100.0;
            networkStats.add(ShortLinkStatsNetworkRespDTO.builder().network(item.getNetwork()).cnt(item.getCnt()).ratio(actualRatio).build());
        });

        // 高频ip访问详情
        List<ShortLinkStatsTopIpRespDTO> topIpStats = new ArrayList<>();
        List<HashMap<String, Object>> listTopIpByShortLink = linkAccessLogsMapper.listTopIpByGroup(reqDTO);
        listTopIpByShortLink.forEach(item -> {
            topIpStats.add(ShortLinkStatsTopIpRespDTO.builder().ip(item.get("ip").toString()).cnt(Integer.parseInt(item.get("count").toString())).build());
        });

        // 小时访问详情
        List<Integer> hourStats = new ArrayList<>();
        List<TLinkAccessStats> listedHourStatsByShortLink = linkAccessStatsMapper.listHourStatsByGroup(reqDTO);
        for (int i = 0; i < 24; i++) {
            AtomicInteger hour = new AtomicInteger(i);
            int hourCnt = listedHourStatsByShortLink.stream()
                    .filter(each -> Objects.equals(each.getHour(), hour.get()))
                    .findFirst()
                    .map(TLinkAccessStats::getPv)
                    .orElse(0);
            hourStats.add(hourCnt);
        }

        // 一周访问详情
        List<Integer> weekdayStats = new ArrayList<>();
        List<TLinkAccessStats> listWeekdayStatsByShortLink = linkAccessStatsMapper.listWeekdayStatsByGroup(reqDTO);
        for (int i = 1; i < 8; i++) {
            AtomicInteger weekday = new AtomicInteger(i);
            int weekdayCnt = listWeekdayStatsByShortLink.stream()
                    .filter(each -> Objects.equals(each.getWeekday(), weekday.get()))
                    .findFirst()
                    .map(TLinkAccessStats::getPv)
                    .orElse(0);
            weekdayStats.add(weekdayCnt);
        }

        // 封装返回结果
        return ShortLinkStatsRespDTO.builder()
                .hourStats(hourStats)
                .browserStats(browserStats)
                .osStats(osStats)
                .localeCnStats(localeCntStats)
                .deviceStats(deviceStats)
                .networkStats(networkStats)
                .pv(pvUvUidStatsByShortLink.getPv())
                .uv(pvUvUidStatsByShortLink.getUv())
                .uip(pvUvUidStatsByShortLink.getUip())
                .daily(daily)
                .topIpStats(topIpStats)
                .weekdayStats(weekdayStats)
                .build();
    }
}
